Merge "Normalize `input[type="search"]`"
[lhc/web/wiklou.git] / tests / phpunit / includes / WatchedItemStoreUnitTest.php
1 <?php
2
3 /**
4 * @author Addshore
5 *
6 * @covers WatchedItemStore
7 */
8 class WatchedItemStoreUnitTest extends MediaWikiTestCase {
9
10 /**
11 * @return PHPUnit_Framework_MockObject_MockObject|IDatabase
12 */
13 private function getMockDb() {
14 return $this->getMock( IDatabase::class );
15 }
16
17 /**
18 * @return PHPUnit_Framework_MockObject_MockObject|LoadBalancer
19 */
20 private function getMockLoadBalancer( $mockDb, $expectedConnectionType = null ) {
21 $mock = $this->getMockBuilder( LoadBalancer::class )
22 ->disableOriginalConstructor()
23 ->getMock();
24 if ( $expectedConnectionType !== null ) {
25 $mock->expects( $this->any() )
26 ->method( 'getConnection' )
27 ->with( $expectedConnectionType )
28 ->will( $this->returnValue( $mockDb ) );
29 } else {
30 $mock->expects( $this->any() )
31 ->method( 'getConnection' )
32 ->will( $this->returnValue( $mockDb ) );
33 }
34 $mock->expects( $this->any() )
35 ->method( 'getReadOnlyReason' )
36 ->will( $this->returnValue( false ) );
37 return $mock;
38 }
39
40 /**
41 * @return PHPUnit_Framework_MockObject_MockObject|HashBagOStuff
42 */
43 private function getMockCache() {
44 $mock = $this->getMockBuilder( HashBagOStuff::class )
45 ->disableOriginalConstructor()
46 ->getMock();
47 $mock->expects( $this->any() )
48 ->method( 'makeKey' )
49 ->will( $this->returnCallback( function() {
50 return implode( ':', func_get_args() );
51 } ) );
52 return $mock;
53 }
54
55 /**
56 * @param int $id
57 * @return PHPUnit_Framework_MockObject_MockObject|User
58 */
59 private function getMockNonAnonUserWithId( $id ) {
60 $mock = $this->getMock( User::class );
61 $mock->expects( $this->any() )
62 ->method( 'isAnon' )
63 ->will( $this->returnValue( false ) );
64 $mock->expects( $this->any() )
65 ->method( 'getId' )
66 ->will( $this->returnValue( $id ) );
67 return $mock;
68 }
69
70 /**
71 * @return User
72 */
73 private function getAnonUser() {
74 return User::newFromName( 'Anon_User' );
75 }
76
77 private function getFakeRow( array $rowValues ) {
78 $fakeRow = new stdClass();
79 foreach ( $rowValues as $valueName => $value ) {
80 $fakeRow->$valueName = $value;
81 }
82 return $fakeRow;
83 }
84
85 private function newWatchedItemStore( LoadBalancer $loadBalancer, HashBagOStuff $cache ) {
86 return new WatchedItemStore(
87 $loadBalancer,
88 $cache
89 );
90 }
91
92 public function testGetDefaultInstance() {
93 $instanceOne = WatchedItemStore::getDefaultInstance();
94 $instanceTwo = WatchedItemStore::getDefaultInstance();
95 $this->assertSame( $instanceOne, $instanceTwo );
96 }
97
98 public function testCountWatchedItems() {
99 $user = $this->getMockNonAnonUserWithId( 1 );
100
101 $mockDb = $this->getMockDb();
102 $mockDb->expects( $this->exactly( 1 ) )
103 ->method( 'selectField' )
104 ->with(
105 'watchlist',
106 'COUNT(*)',
107 [
108 'wl_user' => $user->getId(),
109 ],
110 $this->isType( 'string' )
111 )
112 ->will( $this->returnValue( 12 ) );
113
114 $mockCache = $this->getMockCache();
115 $mockCache->expects( $this->never() )->method( 'get' );
116 $mockCache->expects( $this->never() )->method( 'set' );
117 $mockCache->expects( $this->never() )->method( 'delete' );
118
119 $store = $this->newWatchedItemStore(
120 $this->getMockLoadBalancer( $mockDb ),
121 $mockCache
122 );
123
124 $this->assertEquals( 12, $store->countWatchedItems( $user ) );
125 }
126
127 public function testCountWatchers() {
128 $titleValue = new TitleValue( 0, 'SomeDbKey' );
129
130 $mockDb = $this->getMockDb();
131 $mockDb->expects( $this->exactly( 1 ) )
132 ->method( 'selectField' )
133 ->with(
134 'watchlist',
135 'COUNT(*)',
136 [
137 'wl_namespace' => $titleValue->getNamespace(),
138 'wl_title' => $titleValue->getDBkey(),
139 ],
140 $this->isType( 'string' )
141 )
142 ->will( $this->returnValue( 7 ) );
143
144 $mockCache = $this->getMockCache();
145 $mockCache->expects( $this->never() )->method( 'get' );
146 $mockCache->expects( $this->never() )->method( 'set' );
147 $mockCache->expects( $this->never() )->method( 'delete' );
148
149 $store = $this->newWatchedItemStore(
150 $this->getMockLoadBalancer( $mockDb ),
151 $mockCache
152 );
153
154 $this->assertEquals( 7, $store->countWatchers( $titleValue ) );
155 }
156
157 public function testCountWatchersMultiple() {
158 $titleValues = [
159 new TitleValue( 0, 'SomeDbKey' ),
160 new TitleValue( 0, 'OtherDbKey' ),
161 new TitleValue( 1, 'AnotherDbKey' ),
162 ];
163
164 $mockDb = $this->getMockDb();
165
166 $dbResult = [
167 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
168 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
169 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
170 ),
171 ];
172 $mockDb->expects( $this->once() )
173 ->method( 'makeWhereFrom2d' )
174 ->with(
175 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
176 $this->isType( 'string' ),
177 $this->isType( 'string' )
178 )
179 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
180 $mockDb->expects( $this->once() )
181 ->method( 'select' )
182 ->with(
183 'watchlist',
184 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
185 [ 'makeWhereFrom2d return value' ],
186 $this->isType( 'string' ),
187 [
188 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
189 ]
190 )
191 ->will(
192 $this->returnValue( $dbResult )
193 );
194
195 $mockCache = $this->getMockCache();
196 $mockCache->expects( $this->never() )->method( 'get' );
197 $mockCache->expects( $this->never() )->method( 'set' );
198 $mockCache->expects( $this->never() )->method( 'delete' );
199
200 $store = $this->newWatchedItemStore(
201 $this->getMockLoadBalancer( $mockDb ),
202 $mockCache
203 );
204
205 $expected = [
206 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
207 1 => [ 'AnotherDbKey' => 500 ],
208 ];
209 $this->assertEquals( $expected, $store->countWatchersMultiple( $titleValues ) );
210 }
211
212 public function provideIntWithDbUnsafeVersion() {
213 return [
214 [ 50 ],
215 [ "50; DROP TABLE watchlist;\n--" ],
216 ];
217 }
218
219 /**
220 * @dataProvider provideIntWithDbUnsafeVersion
221 */
222 public function testCountWatchersMultiple_withMinimumWatchers( $minWatchers ) {
223 $titleValues = [
224 new TitleValue( 0, 'SomeDbKey' ),
225 new TitleValue( 0, 'OtherDbKey' ),
226 new TitleValue( 1, 'AnotherDbKey' ),
227 ];
228
229 $mockDb = $this->getMockDb();
230
231 $dbResult = [
232 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
233 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
234 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ]
235 ),
236 ];
237 $mockDb->expects( $this->once() )
238 ->method( 'makeWhereFrom2d' )
239 ->with(
240 [ [ 'SomeDbKey' => 1, 'OtherDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
241 $this->isType( 'string' ),
242 $this->isType( 'string' )
243 )
244 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
245 $mockDb->expects( $this->once() )
246 ->method( 'select' )
247 ->with(
248 'watchlist',
249 [ 'wl_title', 'wl_namespace', 'watchers' => 'COUNT(*)' ],
250 [ 'makeWhereFrom2d return value' ],
251 $this->isType( 'string' ),
252 [
253 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
254 'HAVING' => 'COUNT(*) >= 50',
255 ]
256 )
257 ->will(
258 $this->returnValue( $dbResult )
259 );
260
261 $mockCache = $this->getMockCache();
262 $mockCache->expects( $this->never() )->method( 'get' );
263 $mockCache->expects( $this->never() )->method( 'set' );
264 $mockCache->expects( $this->never() )->method( 'delete' );
265
266 $store = $this->newWatchedItemStore(
267 $this->getMockLoadBalancer( $mockDb ),
268 $mockCache
269 );
270
271 $expected = [
272 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
273 1 => [ 'AnotherDbKey' => 500 ],
274 ];
275 $this->assertEquals(
276 $expected,
277 $store->countWatchersMultiple( $titleValues, [ 'minimumWatchers' => $minWatchers ] )
278 );
279 }
280
281 public function testCountVisitingWatchers() {
282 $titleValue = new TitleValue( 0, 'SomeDbKey' );
283
284 $mockDb = $this->getMockDb();
285 $mockDb->expects( $this->exactly( 1 ) )
286 ->method( 'selectField' )
287 ->with(
288 'watchlist',
289 'COUNT(*)',
290 [
291 'wl_namespace' => $titleValue->getNamespace(),
292 'wl_title' => $titleValue->getDBkey(),
293 'wl_notificationtimestamp >= \'TS111TS\' OR wl_notificationtimestamp IS NULL',
294 ],
295 $this->isType( 'string' )
296 )
297 ->will( $this->returnValue( 7 ) );
298 $mockDb->expects( $this->exactly( 1 ) )
299 ->method( 'addQuotes' )
300 ->will( $this->returnCallback( function( $value ) {
301 return "'$value'";
302 } ) );
303 $mockDb->expects( $this->exactly( 1 ) )
304 ->method( 'timestamp' )
305 ->will( $this->returnCallback( function( $value ) {
306 return 'TS' . $value . 'TS';
307 } ) );
308
309 $mockCache = $this->getMockCache();
310 $mockCache->expects( $this->never() )->method( 'set' );
311 $mockCache->expects( $this->never() )->method( 'get' );
312 $mockCache->expects( $this->never() )->method( 'delete' );
313
314 $store = $this->newWatchedItemStore(
315 $this->getMockLoadBalancer( $mockDb ),
316 $mockCache
317 );
318
319 $this->assertEquals( 7, $store->countVisitingWatchers( $titleValue, '111' ) );
320 }
321
322 public function testCountVisitingWatchersMultiple() {
323 $titleValuesWithThresholds = [
324 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
325 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
326 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
327 ];
328
329 $dbResult = [
330 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
331 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
332 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
333 ];
334 $mockDb = $this->getMockDb();
335 $mockDb->expects( $this->exactly( 2 * 3 ) )
336 ->method( 'addQuotes' )
337 ->will( $this->returnCallback( function( $value ) {
338 return "'$value'";
339 } ) );
340 $mockDb->expects( $this->exactly( 3 ) )
341 ->method( 'timestamp' )
342 ->will( $this->returnCallback( function( $value ) {
343 return 'TS' . $value . 'TS';
344 } ) );
345 $mockDb->expects( $this->any() )
346 ->method( 'makeList' )
347 ->with(
348 $this->isType( 'array' ),
349 $this->isType( 'int' )
350 )
351 ->will( $this->returnCallback( function( $a, $conj ) {
352 $sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
353 return join( $sqlConj, array_map( function( $s ) {
354 return '(' . $s . ')';
355 }, $a
356 ) );
357 } ) );
358 $mockDb->expects( $this->never() )
359 ->method( 'makeWhereFrom2d' );
360
361 $expectedCond =
362 '((wl_namespace = 0) AND (' .
363 "(((wl_title = 'SomeDbKey') AND (" .
364 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
365 ')) OR (' .
366 "(wl_title = 'OtherDbKey') AND (" .
367 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
368 '))))' .
369 ') OR ((wl_namespace = 1) AND (' .
370 "(((wl_title = 'AnotherDbKey') AND (".
371 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
372 ')))))';
373 $mockDb->expects( $this->once() )
374 ->method( 'select' )
375 ->with(
376 'watchlist',
377 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
378 $expectedCond,
379 $this->isType( 'string' ),
380 [
381 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
382 ]
383 )
384 ->will(
385 $this->returnValue( $dbResult )
386 );
387
388 $mockCache = $this->getMockCache();
389 $mockCache->expects( $this->never() )->method( 'get' );
390 $mockCache->expects( $this->never() )->method( 'set' );
391 $mockCache->expects( $this->never() )->method( 'delete' );
392
393 $store = $this->newWatchedItemStore(
394 $this->getMockLoadBalancer( $mockDb ),
395 $mockCache
396 );
397
398 $expected = [
399 0 => [ 'SomeDbKey' => 100, 'OtherDbKey' => 300 ],
400 1 => [ 'AnotherDbKey' => 500 ],
401 ];
402 $this->assertEquals(
403 $expected,
404 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
405 );
406 }
407
408 public function testCountVisitingWatchersMultiple_withMissingTargets() {
409 $titleValuesWithThresholds = [
410 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
411 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
412 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
413 [ new TitleValue( 0, 'SomeNotExisitingDbKey' ), null ],
414 [ new TitleValue( 0, 'OtherNotExisitingDbKey' ), null ],
415 ];
416
417 $dbResult = [
418 $this->getFakeRow( [ 'wl_title' => 'SomeDbKey', 'wl_namespace' => 0, 'watchers' => 100 ] ),
419 $this->getFakeRow( [ 'wl_title' => 'OtherDbKey', 'wl_namespace' => 0, 'watchers' => 300 ] ),
420 $this->getFakeRow( [ 'wl_title' => 'AnotherDbKey', 'wl_namespace' => 1, 'watchers' => 500 ] ),
421 $this->getFakeRow(
422 [ 'wl_title' => 'SomeNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 100 ]
423 ),
424 $this->getFakeRow(
425 [ 'wl_title' => 'OtherNotExisitingDbKey', 'wl_namespace' => 0, 'watchers' => 200 ]
426 ),
427 ];
428 $mockDb = $this->getMockDb();
429 $mockDb->expects( $this->exactly( 2 * 3 ) )
430 ->method( 'addQuotes' )
431 ->will( $this->returnCallback( function( $value ) {
432 return "'$value'";
433 } ) );
434 $mockDb->expects( $this->exactly( 3 ) )
435 ->method( 'timestamp' )
436 ->will( $this->returnCallback( function( $value ) {
437 return 'TS' . $value . 'TS';
438 } ) );
439 $mockDb->expects( $this->any() )
440 ->method( 'makeList' )
441 ->with(
442 $this->isType( 'array' ),
443 $this->isType( 'int' )
444 )
445 ->will( $this->returnCallback( function( $a, $conj ) {
446 $sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
447 return join( $sqlConj, array_map( function( $s ) {
448 return '(' . $s . ')';
449 }, $a
450 ) );
451 } ) );
452 $mockDb->expects( $this->once() )
453 ->method( 'makeWhereFrom2d' )
454 ->with(
455 [ [ 'SomeNotExisitingDbKey' => 1, 'OtherNotExisitingDbKey' => 1 ] ],
456 $this->isType( 'string' ),
457 $this->isType( 'string' )
458 )
459 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
460
461 $expectedCond =
462 '((wl_namespace = 0) AND (' .
463 "(((wl_title = 'SomeDbKey') AND (" .
464 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
465 ')) OR (' .
466 "(wl_title = 'OtherDbKey') AND (" .
467 "(wl_notificationtimestamp >= 'TS111TS') OR (wl_notificationtimestamp IS NULL)" .
468 '))))' .
469 ') OR ((wl_namespace = 1) AND (' .
470 "(((wl_title = 'AnotherDbKey') AND (".
471 "(wl_notificationtimestamp >= 'TS123TS') OR (wl_notificationtimestamp IS NULL)" .
472 '))))' .
473 ') OR ' .
474 '(makeWhereFrom2d return value)';
475 $mockDb->expects( $this->once() )
476 ->method( 'select' )
477 ->with(
478 'watchlist',
479 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
480 $expectedCond,
481 $this->isType( 'string' ),
482 [
483 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
484 ]
485 )
486 ->will(
487 $this->returnValue( $dbResult )
488 );
489
490 $mockCache = $this->getMockCache();
491 $mockCache->expects( $this->never() )->method( 'get' );
492 $mockCache->expects( $this->never() )->method( 'set' );
493 $mockCache->expects( $this->never() )->method( 'delete' );
494
495 $store = $this->newWatchedItemStore(
496 $this->getMockLoadBalancer( $mockDb ),
497 $mockCache
498 );
499
500 $expected = [
501 0 => [
502 'SomeDbKey' => 100, 'OtherDbKey' => 300,
503 'SomeNotExisitingDbKey' => 100, 'OtherNotExisitingDbKey' => 200
504 ],
505 1 => [ 'AnotherDbKey' => 500 ],
506 ];
507 $this->assertEquals(
508 $expected,
509 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds )
510 );
511 }
512
513 /**
514 * @dataProvider provideIntWithDbUnsafeVersion
515 */
516 public function testCountVisitingWatchersMultiple_withMinimumWatchers( $minWatchers ) {
517 $titleValuesWithThresholds = [
518 [ new TitleValue( 0, 'SomeDbKey' ), '111' ],
519 [ new TitleValue( 0, 'OtherDbKey' ), '111' ],
520 [ new TitleValue( 1, 'AnotherDbKey' ), '123' ],
521 ];
522
523 $mockDb = $this->getMockDb();
524 $mockDb->expects( $this->any() )
525 ->method( 'makeList' )
526 ->will( $this->returnValue( 'makeList return value' ) );
527 $mockDb->expects( $this->once() )
528 ->method( 'select' )
529 ->with(
530 'watchlist',
531 [ 'wl_namespace', 'wl_title', 'watchers' => 'COUNT(*)' ],
532 'makeList return value',
533 $this->isType( 'string' ),
534 [
535 'GROUP BY' => [ 'wl_namespace', 'wl_title' ],
536 'HAVING' => 'COUNT(*) >= 50',
537 ]
538 )
539 ->will(
540 $this->returnValue( [] )
541 );
542
543 $mockCache = $this->getMockCache();
544 $mockCache->expects( $this->never() )->method( 'get' );
545 $mockCache->expects( $this->never() )->method( 'set' );
546 $mockCache->expects( $this->never() )->method( 'delete' );
547
548 $store = $this->newWatchedItemStore(
549 $this->getMockLoadBalancer( $mockDb ),
550 $mockCache
551 );
552
553 $expected = [
554 0 => [ 'SomeDbKey' => 0, 'OtherDbKey' => 0 ],
555 1 => [ 'AnotherDbKey' => 0 ],
556 ];
557 $this->assertEquals(
558 $expected,
559 $store->countVisitingWatchersMultiple( $titleValuesWithThresholds, $minWatchers )
560 );
561 }
562
563 public function testCountUnreadNotifications() {
564 $user = $this->getMockNonAnonUserWithId( 1 );
565
566 $mockDb = $this->getMockDb();
567 $mockDb->expects( $this->exactly( 1 ) )
568 ->method( 'selectRowCount' )
569 ->with(
570 'watchlist',
571 '1',
572 [
573 "wl_notificationtimestamp IS NOT NULL",
574 'wl_user' => 1,
575 ],
576 $this->isType( 'string' )
577 )
578 ->will( $this->returnValue( 9 ) );
579
580 $mockCache = $this->getMockCache();
581 $mockCache->expects( $this->never() )->method( 'set' );
582 $mockCache->expects( $this->never() )->method( 'get' );
583 $mockCache->expects( $this->never() )->method( 'delete' );
584
585 $store = $this->newWatchedItemStore(
586 $this->getMockLoadBalancer( $mockDb ),
587 $mockCache
588 );
589
590 $this->assertEquals( 9, $store->countUnreadNotifications( $user ) );
591 }
592
593 /**
594 * @dataProvider provideIntWithDbUnsafeVersion
595 */
596 public function testCountUnreadNotifications_withUnreadLimit_overLimit( $limit ) {
597 $user = $this->getMockNonAnonUserWithId( 1 );
598
599 $mockDb = $this->getMockDb();
600 $mockDb->expects( $this->exactly( 1 ) )
601 ->method( 'selectRowCount' )
602 ->with(
603 'watchlist',
604 '1',
605 [
606 "wl_notificationtimestamp IS NOT NULL",
607 'wl_user' => 1,
608 ],
609 $this->isType( 'string' ),
610 [ 'LIMIT' => 50 ]
611 )
612 ->will( $this->returnValue( 50 ) );
613
614 $mockCache = $this->getMockCache();
615 $mockCache->expects( $this->never() )->method( 'set' );
616 $mockCache->expects( $this->never() )->method( 'get' );
617 $mockCache->expects( $this->never() )->method( 'delete' );
618
619 $store = $this->newWatchedItemStore(
620 $this->getMockLoadBalancer( $mockDb ),
621 $mockCache
622 );
623
624 $this->assertSame(
625 true,
626 $store->countUnreadNotifications( $user, $limit )
627 );
628 }
629
630 /**
631 * @dataProvider provideIntWithDbUnsafeVersion
632 */
633 public function testCountUnreadNotifications_withUnreadLimit_underLimit( $limit ) {
634 $user = $this->getMockNonAnonUserWithId( 1 );
635
636 $mockDb = $this->getMockDb();
637 $mockDb->expects( $this->exactly( 1 ) )
638 ->method( 'selectRowCount' )
639 ->with(
640 'watchlist',
641 '1',
642 [
643 "wl_notificationtimestamp IS NOT NULL",
644 'wl_user' => 1,
645 ],
646 $this->isType( 'string' ),
647 [ 'LIMIT' => 50 ]
648 )
649 ->will( $this->returnValue( 9 ) );
650
651 $mockCache = $this->getMockCache();
652 $mockCache->expects( $this->never() )->method( 'set' );
653 $mockCache->expects( $this->never() )->method( 'get' );
654 $mockCache->expects( $this->never() )->method( 'delete' );
655
656 $store = $this->newWatchedItemStore(
657 $this->getMockLoadBalancer( $mockDb ),
658 $mockCache
659 );
660
661 $this->assertEquals(
662 9,
663 $store->countUnreadNotifications( $user, $limit )
664 );
665 }
666
667 public function testDuplicateEntry_nothingToDuplicate() {
668 $mockDb = $this->getMockDb();
669 $mockDb->expects( $this->once() )
670 ->method( 'select' )
671 ->with(
672 'watchlist',
673 [
674 'wl_user',
675 'wl_notificationtimestamp',
676 ],
677 [
678 'wl_namespace' => 0,
679 'wl_title' => 'Old_Title',
680 ],
681 'WatchedItemStore::duplicateEntry',
682 [ 'FOR UPDATE' ]
683 )
684 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
685
686 $store = $this->newWatchedItemStore(
687 $this->getMockLoadBalancer( $mockDb ),
688 $this->getMockCache()
689 );
690
691 $store->duplicateEntry(
692 Title::newFromText( 'Old_Title' ),
693 Title::newFromText( 'New_Title' )
694 );
695 }
696
697 public function testDuplicateEntry_somethingToDuplicate() {
698 $fakeRows = [
699 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
700 $this->getFakeRow( [ 'wl_user' => 2, 'wl_notificationtimestamp' => null ] ),
701 ];
702
703 $mockDb = $this->getMockDb();
704 $mockDb->expects( $this->at( 0 ) )
705 ->method( 'select' )
706 ->with(
707 'watchlist',
708 [
709 'wl_user',
710 'wl_notificationtimestamp',
711 ],
712 [
713 'wl_namespace' => 0,
714 'wl_title' => 'Old_Title',
715 ]
716 )
717 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
718 $mockDb->expects( $this->at( 1 ) )
719 ->method( 'replace' )
720 ->with(
721 'watchlist',
722 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
723 [
724 [
725 'wl_user' => 1,
726 'wl_namespace' => 0,
727 'wl_title' => 'New_Title',
728 'wl_notificationtimestamp' => '20151212010101',
729 ],
730 [
731 'wl_user' => 2,
732 'wl_namespace' => 0,
733 'wl_title' => 'New_Title',
734 'wl_notificationtimestamp' => null,
735 ],
736 ],
737 $this->isType( 'string' )
738 );
739
740 $mockCache = $this->getMockCache();
741 $mockCache->expects( $this->never() )->method( 'get' );
742 $mockCache->expects( $this->never() )->method( 'delete' );
743
744 $store = $this->newWatchedItemStore(
745 $this->getMockLoadBalancer( $mockDb ),
746 $mockCache
747 );
748
749 $store->duplicateEntry(
750 Title::newFromText( 'Old_Title' ),
751 Title::newFromText( 'New_Title' )
752 );
753 }
754
755 public function testDuplicateAllAssociatedEntries_nothingToDuplicate() {
756 $mockDb = $this->getMockDb();
757 $mockDb->expects( $this->at( 0 ) )
758 ->method( 'select' )
759 ->with(
760 'watchlist',
761 [
762 'wl_user',
763 'wl_notificationtimestamp',
764 ],
765 [
766 'wl_namespace' => 0,
767 'wl_title' => 'Old_Title',
768 ]
769 )
770 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
771 $mockDb->expects( $this->at( 1 ) )
772 ->method( 'select' )
773 ->with(
774 'watchlist',
775 [
776 'wl_user',
777 'wl_notificationtimestamp',
778 ],
779 [
780 'wl_namespace' => 1,
781 'wl_title' => 'Old_Title',
782 ]
783 )
784 ->will( $this->returnValue( new FakeResultWrapper( [] ) ) );
785
786 $mockCache = $this->getMockCache();
787 $mockCache->expects( $this->never() )->method( 'get' );
788 $mockCache->expects( $this->never() )->method( 'delete' );
789
790 $store = $this->newWatchedItemStore(
791 $this->getMockLoadBalancer( $mockDb ),
792 $mockCache
793 );
794
795 $store->duplicateAllAssociatedEntries(
796 Title::newFromText( 'Old_Title' ),
797 Title::newFromText( 'New_Title' )
798 );
799 }
800
801 public function testDuplicateAllAssociatedEntries_somethingToDuplicate() {
802 $fakeRows = [
803 $this->getFakeRow( [ 'wl_user' => 1, 'wl_notificationtimestamp' => '20151212010101' ] ),
804 ];
805
806 $mockDb = $this->getMockDb();
807 $mockDb->expects( $this->at( 0 ) )
808 ->method( 'select' )
809 ->with(
810 'watchlist',
811 [
812 'wl_user',
813 'wl_notificationtimestamp',
814 ],
815 [
816 'wl_namespace' => 0,
817 'wl_title' => 'Old_Title',
818 ]
819 )
820 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
821 $mockDb->expects( $this->at( 1 ) )
822 ->method( 'replace' )
823 ->with(
824 'watchlist',
825 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
826 [
827 [
828 'wl_user' => 1,
829 'wl_namespace' => 0,
830 'wl_title' => 'New_Title',
831 'wl_notificationtimestamp' => '20151212010101',
832 ],
833 ],
834 $this->isType( 'string' )
835 );
836 $mockDb->expects( $this->at( 2 ) )
837 ->method( 'select' )
838 ->with(
839 'watchlist',
840 [
841 'wl_user',
842 'wl_notificationtimestamp',
843 ],
844 [
845 'wl_namespace' => 1,
846 'wl_title' => 'Old_Title',
847 ]
848 )
849 ->will( $this->returnValue( new FakeResultWrapper( $fakeRows ) ) );
850 $mockDb->expects( $this->at( 3 ) )
851 ->method( 'replace' )
852 ->with(
853 'watchlist',
854 [ [ 'wl_user', 'wl_namespace', 'wl_title' ] ],
855 [
856 [
857 'wl_user' => 1,
858 'wl_namespace' => 1,
859 'wl_title' => 'New_Title',
860 'wl_notificationtimestamp' => '20151212010101',
861 ],
862 ],
863 $this->isType( 'string' )
864 );
865
866 $mockCache = $this->getMockCache();
867 $mockCache->expects( $this->never() )->method( 'get' );
868 $mockCache->expects( $this->never() )->method( 'delete' );
869
870 $store = $this->newWatchedItemStore(
871 $this->getMockLoadBalancer( $mockDb ),
872 $mockCache
873 );
874
875 $store->duplicateAllAssociatedEntries(
876 Title::newFromText( 'Old_Title' ),
877 Title::newFromText( 'New_Title' )
878 );
879 }
880
881 public function testAddWatch_nonAnonymousUser() {
882 $mockDb = $this->getMockDb();
883 $mockDb->expects( $this->once() )
884 ->method( 'insert' )
885 ->with(
886 'watchlist',
887 [
888 [
889 'wl_user' => 1,
890 'wl_namespace' => 0,
891 'wl_title' => 'Some_Page',
892 'wl_notificationtimestamp' => null,
893 ]
894 ]
895 );
896
897 $mockCache = $this->getMockCache();
898 $mockCache->expects( $this->once() )
899 ->method( 'delete' )
900 ->with( '0:Some_Page:1' );
901
902 $store = $this->newWatchedItemStore(
903 $this->getMockLoadBalancer( $mockDb ),
904 $mockCache
905 );
906
907 $store->addWatch(
908 $this->getMockNonAnonUserWithId( 1 ),
909 Title::newFromText( 'Some_Page' )
910 );
911 }
912
913 public function testAddWatch_anonymousUser() {
914 $mockDb = $this->getMockDb();
915 $mockDb->expects( $this->never() )
916 ->method( 'insert' );
917
918 $mockCache = $this->getMockCache();
919 $mockCache->expects( $this->never() )
920 ->method( 'delete' );
921
922 $store = $this->newWatchedItemStore(
923 $this->getMockLoadBalancer( $mockDb ),
924 $mockCache
925 );
926
927 $store->addWatch(
928 $this->getAnonUser(),
929 Title::newFromText( 'Some_Page' )
930 );
931 }
932
933 public function testAddWatchBatchForUser_nonAnonymousUser() {
934 $mockDb = $this->getMockDb();
935 $mockDb->expects( $this->once() )
936 ->method( 'insert' )
937 ->with(
938 'watchlist',
939 [
940 [
941 'wl_user' => 1,
942 'wl_namespace' => 0,
943 'wl_title' => 'Some_Page',
944 'wl_notificationtimestamp' => null,
945 ],
946 [
947 'wl_user' => 1,
948 'wl_namespace' => 1,
949 'wl_title' => 'Some_Page',
950 'wl_notificationtimestamp' => null,
951 ]
952 ]
953 );
954
955 $mockCache = $this->getMockCache();
956 $mockCache->expects( $this->exactly( 2 ) )
957 ->method( 'delete' );
958 $mockCache->expects( $this->at( 1 ) )
959 ->method( 'delete' )
960 ->with( '0:Some_Page:1' );
961 $mockCache->expects( $this->at( 3 ) )
962 ->method( 'delete' )
963 ->with( '1:Some_Page:1' );
964
965 $store = $this->newWatchedItemStore(
966 $this->getMockLoadBalancer( $mockDb ),
967 $mockCache
968 );
969
970 $mockUser = $this->getMockNonAnonUserWithId( 1 );
971
972 $this->assertTrue(
973 $store->addWatchBatchForUser(
974 $mockUser,
975 [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
976 )
977 );
978 }
979
980 public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
981 $mockDb = $this->getMockDb();
982 $mockDb->expects( $this->never() )
983 ->method( 'insert' );
984
985 $mockCache = $this->getMockCache();
986 $mockCache->expects( $this->never() )
987 ->method( 'delete' );
988
989 $store = $this->newWatchedItemStore(
990 $this->getMockLoadBalancer( $mockDb ),
991 $mockCache
992 );
993
994 $this->assertFalse(
995 $store->addWatchBatchForUser(
996 $this->getAnonUser(),
997 [ new TitleValue( 0, 'Other_Page' ) ]
998 )
999 );
1000 }
1001
1002 public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
1003 $user = $this->getMockNonAnonUserWithId( 1 );
1004 $mockDb = $this->getMockDb();
1005 $mockDb->expects( $this->never() )
1006 ->method( 'insert' );
1007
1008 $mockCache = $this->getMockCache();
1009 $mockCache->expects( $this->never() )
1010 ->method( 'delete' );
1011
1012 $store = $this->newWatchedItemStore(
1013 $this->getMockLoadBalancer( $mockDb ),
1014 $mockCache
1015 );
1016
1017 $this->assertTrue(
1018 $store->addWatchBatchForUser( $user, [] )
1019 );
1020 }
1021
1022 public function testLoadWatchedItem_existingItem() {
1023 $mockDb = $this->getMockDb();
1024 $mockDb->expects( $this->once() )
1025 ->method( 'selectRow' )
1026 ->with(
1027 'watchlist',
1028 'wl_notificationtimestamp',
1029 [
1030 'wl_user' => 1,
1031 'wl_namespace' => 0,
1032 'wl_title' => 'SomeDbKey',
1033 ]
1034 )
1035 ->will( $this->returnValue(
1036 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1037 ) );
1038
1039 $mockCache = $this->getMockCache();
1040 $mockCache->expects( $this->once() )
1041 ->method( 'set' )
1042 ->with(
1043 '0:SomeDbKey:1'
1044 );
1045
1046 $store = $this->newWatchedItemStore(
1047 $this->getMockLoadBalancer( $mockDb ),
1048 $mockCache
1049 );
1050
1051 $watchedItem = $store->loadWatchedItem(
1052 $this->getMockNonAnonUserWithId( 1 ),
1053 new TitleValue( 0, 'SomeDbKey' )
1054 );
1055 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1056 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1057 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1058 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1059 }
1060
1061 public function testLoadWatchedItem_noItem() {
1062 $mockDb = $this->getMockDb();
1063 $mockDb->expects( $this->once() )
1064 ->method( 'selectRow' )
1065 ->with(
1066 'watchlist',
1067 'wl_notificationtimestamp',
1068 [
1069 'wl_user' => 1,
1070 'wl_namespace' => 0,
1071 'wl_title' => 'SomeDbKey',
1072 ]
1073 )
1074 ->will( $this->returnValue( [] ) );
1075
1076 $mockCache = $this->getMockCache();
1077 $mockCache->expects( $this->never() )->method( 'get' );
1078 $mockCache->expects( $this->never() )->method( 'delete' );
1079
1080 $store = $this->newWatchedItemStore(
1081 $this->getMockLoadBalancer( $mockDb ),
1082 $mockCache
1083 );
1084
1085 $this->assertFalse(
1086 $store->loadWatchedItem(
1087 $this->getMockNonAnonUserWithId( 1 ),
1088 new TitleValue( 0, 'SomeDbKey' )
1089 )
1090 );
1091 }
1092
1093 public function testLoadWatchedItem_anonymousUser() {
1094 $mockDb = $this->getMockDb();
1095 $mockDb->expects( $this->never() )
1096 ->method( 'selectRow' );
1097
1098 $mockCache = $this->getMockCache();
1099 $mockCache->expects( $this->never() )->method( 'get' );
1100 $mockCache->expects( $this->never() )->method( 'delete' );
1101
1102 $store = $this->newWatchedItemStore(
1103 $this->getMockLoadBalancer( $mockDb ),
1104 $mockCache
1105 );
1106
1107 $this->assertFalse(
1108 $store->loadWatchedItem(
1109 $this->getAnonUser(),
1110 new TitleValue( 0, 'SomeDbKey' )
1111 )
1112 );
1113 }
1114
1115 public function testRemoveWatch_existingItem() {
1116 $mockDb = $this->getMockDb();
1117 $mockDb->expects( $this->once() )
1118 ->method( 'delete' )
1119 ->with(
1120 'watchlist',
1121 [
1122 'wl_user' => 1,
1123 'wl_namespace' => 0,
1124 'wl_title' => 'SomeDbKey',
1125 ]
1126 );
1127 $mockDb->expects( $this->once() )
1128 ->method( 'affectedRows' )
1129 ->will( $this->returnValue( 1 ) );
1130
1131 $mockCache = $this->getMockCache();
1132 $mockCache->expects( $this->never() )->method( 'get' );
1133 $mockCache->expects( $this->once() )
1134 ->method( 'delete' )
1135 ->with( '0:SomeDbKey:1' );
1136
1137 $store = $this->newWatchedItemStore(
1138 $this->getMockLoadBalancer( $mockDb ),
1139 $mockCache
1140 );
1141
1142 $this->assertTrue(
1143 $store->removeWatch(
1144 $this->getMockNonAnonUserWithId( 1 ),
1145 new TitleValue( 0, 'SomeDbKey' )
1146 )
1147 );
1148 }
1149
1150 public function testRemoveWatch_noItem() {
1151 $mockDb = $this->getMockDb();
1152 $mockDb->expects( $this->once() )
1153 ->method( 'delete' )
1154 ->with(
1155 'watchlist',
1156 [
1157 'wl_user' => 1,
1158 'wl_namespace' => 0,
1159 'wl_title' => 'SomeDbKey',
1160 ]
1161 );
1162 $mockDb->expects( $this->once() )
1163 ->method( 'affectedRows' )
1164 ->will( $this->returnValue( 0 ) );
1165
1166 $mockCache = $this->getMockCache();
1167 $mockCache->expects( $this->never() )->method( 'get' );
1168 $mockCache->expects( $this->once() )
1169 ->method( 'delete' )
1170 ->with( '0:SomeDbKey:1' );
1171
1172 $store = $this->newWatchedItemStore(
1173 $this->getMockLoadBalancer( $mockDb ),
1174 $mockCache
1175 );
1176
1177 $this->assertFalse(
1178 $store->removeWatch(
1179 $this->getMockNonAnonUserWithId( 1 ),
1180 new TitleValue( 0, 'SomeDbKey' )
1181 )
1182 );
1183 }
1184
1185 public function testRemoveWatch_anonymousUser() {
1186 $mockDb = $this->getMockDb();
1187 $mockDb->expects( $this->never() )
1188 ->method( 'delete' );
1189
1190 $mockCache = $this->getMockCache();
1191 $mockCache->expects( $this->never() )->method( 'get' );
1192 $mockCache->expects( $this->never() )
1193 ->method( 'delete' );
1194
1195 $store = $this->newWatchedItemStore(
1196 $this->getMockLoadBalancer( $mockDb ),
1197 $mockCache
1198 );
1199
1200 $this->assertFalse(
1201 $store->removeWatch(
1202 $this->getAnonUser(),
1203 new TitleValue( 0, 'SomeDbKey' )
1204 )
1205 );
1206 }
1207
1208 public function testGetWatchedItem_existingItem() {
1209 $mockDb = $this->getMockDb();
1210 $mockDb->expects( $this->once() )
1211 ->method( 'selectRow' )
1212 ->with(
1213 'watchlist',
1214 'wl_notificationtimestamp',
1215 [
1216 'wl_user' => 1,
1217 'wl_namespace' => 0,
1218 'wl_title' => 'SomeDbKey',
1219 ]
1220 )
1221 ->will( $this->returnValue(
1222 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1223 ) );
1224
1225 $mockCache = $this->getMockCache();
1226 $mockCache->expects( $this->never() )->method( 'delete' );
1227 $mockCache->expects( $this->once() )
1228 ->method( 'get' )
1229 ->with(
1230 '0:SomeDbKey:1'
1231 )
1232 ->will( $this->returnValue( null ) );
1233 $mockCache->expects( $this->once() )
1234 ->method( 'set' )
1235 ->with(
1236 '0:SomeDbKey:1'
1237 );
1238
1239 $store = $this->newWatchedItemStore(
1240 $this->getMockLoadBalancer( $mockDb ),
1241 $mockCache
1242 );
1243
1244 $watchedItem = $store->getWatchedItem(
1245 $this->getMockNonAnonUserWithId( 1 ),
1246 new TitleValue( 0, 'SomeDbKey' )
1247 );
1248 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1249 $this->assertEquals( 1, $watchedItem->getUser()->getId() );
1250 $this->assertEquals( 'SomeDbKey', $watchedItem->getLinkTarget()->getDBkey() );
1251 $this->assertEquals( 0, $watchedItem->getLinkTarget()->getNamespace() );
1252 }
1253
1254 public function testGetWatchedItem_cachedItem() {
1255 $mockDb = $this->getMockDb();
1256 $mockDb->expects( $this->never() )
1257 ->method( 'selectRow' );
1258
1259 $mockUser = $this->getMockNonAnonUserWithId( 1 );
1260 $linkTarget = new TitleValue( 0, 'SomeDbKey' );
1261 $cachedItem = new WatchedItem( $mockUser, $linkTarget, '20151212010101' );
1262
1263 $mockCache = $this->getMockCache();
1264 $mockCache->expects( $this->never() )->method( 'delete' );
1265 $mockCache->expects( $this->never() )->method( 'set' );
1266 $mockCache->expects( $this->once() )
1267 ->method( 'get' )
1268 ->with(
1269 '0:SomeDbKey:1'
1270 )
1271 ->will( $this->returnValue( $cachedItem ) );
1272
1273 $store = $this->newWatchedItemStore(
1274 $this->getMockLoadBalancer( $mockDb ),
1275 $mockCache
1276 );
1277
1278 $this->assertEquals(
1279 $cachedItem,
1280 $store->getWatchedItem(
1281 $mockUser,
1282 $linkTarget
1283 )
1284 );
1285 }
1286
1287 public function testGetWatchedItem_noItem() {
1288 $mockDb = $this->getMockDb();
1289 $mockDb->expects( $this->once() )
1290 ->method( 'selectRow' )
1291 ->with(
1292 'watchlist',
1293 'wl_notificationtimestamp',
1294 [
1295 'wl_user' => 1,
1296 'wl_namespace' => 0,
1297 'wl_title' => 'SomeDbKey',
1298 ]
1299 )
1300 ->will( $this->returnValue( [] ) );
1301
1302 $mockCache = $this->getMockCache();
1303 $mockCache->expects( $this->never() )->method( 'set' );
1304 $mockCache->expects( $this->never() )->method( 'delete' );
1305 $mockCache->expects( $this->once() )
1306 ->method( 'get' )
1307 ->with( '0:SomeDbKey:1' )
1308 ->will( $this->returnValue( false ) );
1309
1310 $store = $this->newWatchedItemStore(
1311 $this->getMockLoadBalancer( $mockDb ),
1312 $mockCache
1313 );
1314
1315 $this->assertFalse(
1316 $store->getWatchedItem(
1317 $this->getMockNonAnonUserWithId( 1 ),
1318 new TitleValue( 0, 'SomeDbKey' )
1319 )
1320 );
1321 }
1322
1323 public function testGetWatchedItem_anonymousUser() {
1324 $mockDb = $this->getMockDb();
1325 $mockDb->expects( $this->never() )
1326 ->method( 'selectRow' );
1327
1328 $mockCache = $this->getMockCache();
1329 $mockCache->expects( $this->never() )->method( 'set' );
1330 $mockCache->expects( $this->never() )->method( 'get' );
1331 $mockCache->expects( $this->never() )->method( 'delete' );
1332
1333 $store = $this->newWatchedItemStore(
1334 $this->getMockLoadBalancer( $mockDb ),
1335 $mockCache
1336 );
1337
1338 $this->assertFalse(
1339 $store->getWatchedItem(
1340 $this->getAnonUser(),
1341 new TitleValue( 0, 'SomeDbKey' )
1342 )
1343 );
1344 }
1345
1346 public function testGetWatchedItemsForUser() {
1347 $mockDb = $this->getMockDb();
1348 $mockDb->expects( $this->once() )
1349 ->method( 'select' )
1350 ->with(
1351 'watchlist',
1352 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1353 [ 'wl_user' => 1 ]
1354 )
1355 ->will( $this->returnValue( [
1356 $this->getFakeRow( [
1357 'wl_namespace' => 0,
1358 'wl_title' => 'Foo1',
1359 'wl_notificationtimestamp' => '20151212010101',
1360 ] ),
1361 $this->getFakeRow( [
1362 'wl_namespace' => 1,
1363 'wl_title' => 'Foo2',
1364 'wl_notificationtimestamp' => null,
1365 ] ),
1366 ] ) );
1367
1368 $mockCache = $this->getMockCache();
1369 $mockCache->expects( $this->never() )->method( 'delete' );
1370 $mockCache->expects( $this->never() )->method( 'get' );
1371 $mockCache->expects( $this->never() )->method( 'set' );
1372
1373 $store = $this->newWatchedItemStore(
1374 $this->getMockLoadBalancer( $mockDb ),
1375 $mockCache
1376 );
1377 $user = $this->getMockNonAnonUserWithId( 1 );
1378
1379 $watchedItems = $store->getWatchedItemsForUser( $user );
1380
1381 $this->assertInternalType( 'array', $watchedItems );
1382 $this->assertCount( 2, $watchedItems );
1383 foreach ( $watchedItems as $watchedItem ) {
1384 $this->assertInstanceOf( 'WatchedItem', $watchedItem );
1385 }
1386 $this->assertEquals(
1387 new WatchedItem( $user, new TitleValue( 0, 'Foo1' ), '20151212010101' ),
1388 $watchedItems[0]
1389 );
1390 $this->assertEquals(
1391 new WatchedItem( $user, new TitleValue( 1, 'Foo2' ), null ),
1392 $watchedItems[1]
1393 );
1394 }
1395
1396 public function provideDbTypes() {
1397 return [
1398 [ false, DB_SLAVE ],
1399 [ true, DB_MASTER ],
1400 ];
1401 }
1402
1403 /**
1404 * @dataProvider provideDbTypes
1405 */
1406 public function testGetWatchedItemsForUser_optionsAndEmptyResult( $forWrite, $dbType ) {
1407 $mockDb = $this->getMockDb();
1408 $mockCache = $this->getMockCache();
1409 $mockLoadBalancer = $this->getMockLoadBalancer( $mockDb, $dbType );
1410 $user = $this->getMockNonAnonUserWithId( 1 );
1411
1412 $mockDb->expects( $this->once() )
1413 ->method( 'select' )
1414 ->with(
1415 'watchlist',
1416 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1417 [ 'wl_user' => 1 ],
1418 $this->isType( 'string' ),
1419 [ 'ORDER BY' => [ 'wl_namespace ASC', 'wl_title ASC' ] ]
1420 )
1421 ->will( $this->returnValue( [] ) );
1422
1423 $store = $this->newWatchedItemStore(
1424 $mockLoadBalancer,
1425 $mockCache
1426 );
1427
1428 $watchedItems = $store->getWatchedItemsForUser(
1429 $user,
1430 [ 'forWrite' => $forWrite, 'sort' => WatchedItemStore::SORT_ASC ]
1431 );
1432 $this->assertEquals( [], $watchedItems );
1433 }
1434
1435 public function testGetWatchedItemsForUser_badSortOptionThrowsException() {
1436 $store = $this->newWatchedItemStore(
1437 $this->getMockLoadBalancer( $this->getMockDb() ),
1438 $this->getMockCache()
1439 );
1440
1441 $this->setExpectedException( 'InvalidArgumentException' );
1442 $store->getWatchedItemsForUser(
1443 $this->getMockNonAnonUserWithId( 1 ),
1444 [ 'sort' => 'foo' ]
1445 );
1446 }
1447
1448 public function testIsWatchedItem_existingItem() {
1449 $mockDb = $this->getMockDb();
1450 $mockDb->expects( $this->once() )
1451 ->method( 'selectRow' )
1452 ->with(
1453 'watchlist',
1454 'wl_notificationtimestamp',
1455 [
1456 'wl_user' => 1,
1457 'wl_namespace' => 0,
1458 'wl_title' => 'SomeDbKey',
1459 ]
1460 )
1461 ->will( $this->returnValue(
1462 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1463 ) );
1464
1465 $mockCache = $this->getMockCache();
1466 $mockCache->expects( $this->never() )->method( 'delete' );
1467 $mockCache->expects( $this->once() )
1468 ->method( 'get' )
1469 ->with( '0:SomeDbKey:1' )
1470 ->will( $this->returnValue( false ) );
1471 $mockCache->expects( $this->once() )
1472 ->method( 'set' )
1473 ->with(
1474 '0:SomeDbKey:1'
1475 );
1476
1477 $store = $this->newWatchedItemStore(
1478 $this->getMockLoadBalancer( $mockDb ),
1479 $mockCache
1480 );
1481
1482 $this->assertTrue(
1483 $store->isWatched(
1484 $this->getMockNonAnonUserWithId( 1 ),
1485 new TitleValue( 0, 'SomeDbKey' )
1486 )
1487 );
1488 }
1489
1490 public function testIsWatchedItem_noItem() {
1491 $mockDb = $this->getMockDb();
1492 $mockDb->expects( $this->once() )
1493 ->method( 'selectRow' )
1494 ->with(
1495 'watchlist',
1496 'wl_notificationtimestamp',
1497 [
1498 'wl_user' => 1,
1499 'wl_namespace' => 0,
1500 'wl_title' => 'SomeDbKey',
1501 ]
1502 )
1503 ->will( $this->returnValue( [] ) );
1504
1505 $mockCache = $this->getMockCache();
1506 $mockCache->expects( $this->never() )->method( 'set' );
1507 $mockCache->expects( $this->never() )->method( 'delete' );
1508 $mockCache->expects( $this->once() )
1509 ->method( 'get' )
1510 ->with( '0:SomeDbKey:1' )
1511 ->will( $this->returnValue( false ) );
1512
1513 $store = $this->newWatchedItemStore(
1514 $this->getMockLoadBalancer( $mockDb ),
1515 $mockCache
1516 );
1517
1518 $this->assertFalse(
1519 $store->isWatched(
1520 $this->getMockNonAnonUserWithId( 1 ),
1521 new TitleValue( 0, 'SomeDbKey' )
1522 )
1523 );
1524 }
1525
1526 public function testIsWatchedItem_anonymousUser() {
1527 $mockDb = $this->getMockDb();
1528 $mockDb->expects( $this->never() )
1529 ->method( 'selectRow' );
1530
1531 $mockCache = $this->getMockCache();
1532 $mockCache->expects( $this->never() )->method( 'set' );
1533 $mockCache->expects( $this->never() )->method( 'get' );
1534 $mockCache->expects( $this->never() )->method( 'delete' );
1535
1536 $store = $this->newWatchedItemStore(
1537 $this->getMockLoadBalancer( $mockDb ),
1538 $mockCache
1539 );
1540
1541 $this->assertFalse(
1542 $store->isWatched(
1543 $this->getAnonUser(),
1544 new TitleValue( 0, 'SomeDbKey' )
1545 )
1546 );
1547 }
1548
1549 public function testGetNotificationTimestampsBatch() {
1550 $targets = [
1551 new TitleValue( 0, 'SomeDbKey' ),
1552 new TitleValue( 1, 'AnotherDbKey' ),
1553 ];
1554
1555 $mockDb = $this->getMockDb();
1556 $dbResult = [
1557 $this->getFakeRow( [
1558 'wl_namespace' => 0,
1559 'wl_title' => 'SomeDbKey',
1560 'wl_notificationtimestamp' => '20151212010101',
1561 ] ),
1562 $this->getFakeRow(
1563 [
1564 'wl_namespace' => 1,
1565 'wl_title' => 'AnotherDbKey',
1566 'wl_notificationtimestamp' => null,
1567 ]
1568 ),
1569 ];
1570
1571 $mockDb->expects( $this->once() )
1572 ->method( 'makeWhereFrom2d' )
1573 ->with(
1574 [ [ 'SomeDbKey' => 1 ], [ 'AnotherDbKey' => 1 ] ],
1575 $this->isType( 'string' ),
1576 $this->isType( 'string' )
1577 )
1578 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1579 $mockDb->expects( $this->once() )
1580 ->method( 'select' )
1581 ->with(
1582 'watchlist',
1583 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1584 [
1585 'makeWhereFrom2d return value',
1586 'wl_user' => 1
1587 ],
1588 $this->isType( 'string' )
1589 )
1590 ->will( $this->returnValue( $dbResult ) );
1591
1592 $mockCache = $this->getMockCache();
1593 $mockCache->expects( $this->exactly( 2 ) )
1594 ->method( 'get' )
1595 ->withConsecutive(
1596 [ '0:SomeDbKey:1' ],
1597 [ '1:AnotherDbKey:1' ]
1598 )
1599 ->will( $this->returnValue( null ) );
1600 $mockCache->expects( $this->never() )->method( 'set' );
1601 $mockCache->expects( $this->never() )->method( 'delete' );
1602
1603 $store = $this->newWatchedItemStore(
1604 $this->getMockLoadBalancer( $mockDb ),
1605 $mockCache
1606 );
1607
1608 $this->assertEquals(
1609 [
1610 0 => [ 'SomeDbKey' => '20151212010101', ],
1611 1 => [ 'AnotherDbKey' => null, ],
1612 ],
1613 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1614 );
1615 }
1616
1617 public function testGetNotificationTimestampsBatch_notWatchedTarget() {
1618 $targets = [
1619 new TitleValue( 0, 'OtherDbKey' ),
1620 ];
1621
1622 $mockDb = $this->getMockDb();
1623
1624 $mockDb->expects( $this->once() )
1625 ->method( 'makeWhereFrom2d' )
1626 ->with(
1627 [ [ 'OtherDbKey' => 1 ] ],
1628 $this->isType( 'string' ),
1629 $this->isType( 'string' )
1630 )
1631 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1632 $mockDb->expects( $this->once() )
1633 ->method( 'select' )
1634 ->with(
1635 'watchlist',
1636 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1637 [
1638 'makeWhereFrom2d return value',
1639 'wl_user' => 1
1640 ],
1641 $this->isType( 'string' )
1642 )
1643 ->will( $this->returnValue( $this->getFakeRow( [] ) ) );
1644
1645 $mockCache = $this->getMockCache();
1646 $mockCache->expects( $this->once() )
1647 ->method( 'get' )
1648 ->with( '0:OtherDbKey:1' )
1649 ->will( $this->returnValue( null ) );
1650 $mockCache->expects( $this->never() )->method( 'set' );
1651 $mockCache->expects( $this->never() )->method( 'delete' );
1652
1653 $store = $this->newWatchedItemStore(
1654 $this->getMockLoadBalancer( $mockDb ),
1655 $mockCache
1656 );
1657
1658 $this->assertEquals(
1659 [
1660 0 => [ 'OtherDbKey' => false, ],
1661 ],
1662 $store->getNotificationTimestampsBatch( $this->getMockNonAnonUserWithId( 1 ), $targets )
1663 );
1664 }
1665
1666 public function testGetNotificationTimestampsBatch_cachedItem() {
1667 $targets = [
1668 new TitleValue( 0, 'SomeDbKey' ),
1669 new TitleValue( 1, 'AnotherDbKey' ),
1670 ];
1671
1672 $user = $this->getMockNonAnonUserWithId( 1 );
1673 $cachedItem = new WatchedItem( $user, $targets[0], '20151212010101' );
1674
1675 $mockDb = $this->getMockDb();
1676
1677 $mockDb->expects( $this->once() )
1678 ->method( 'makeWhereFrom2d' )
1679 ->with(
1680 [ 1 => [ 'AnotherDbKey' => 1 ] ],
1681 $this->isType( 'string' ),
1682 $this->isType( 'string' )
1683 )
1684 ->will( $this->returnValue( 'makeWhereFrom2d return value' ) );
1685 $mockDb->expects( $this->once() )
1686 ->method( 'select' )
1687 ->with(
1688 'watchlist',
1689 [ 'wl_namespace', 'wl_title', 'wl_notificationtimestamp' ],
1690 [
1691 'makeWhereFrom2d return value',
1692 'wl_user' => 1
1693 ],
1694 $this->isType( 'string' )
1695 )
1696 ->will( $this->returnValue( [
1697 $this->getFakeRow(
1698 [ 'wl_namespace' => 1, 'wl_title' => 'AnotherDbKey', 'wl_notificationtimestamp' => null, ]
1699 )
1700 ] ) );
1701
1702 $mockCache = $this->getMockCache();
1703 $mockCache->expects( $this->at( 1 ) )
1704 ->method( 'get' )
1705 ->with( '0:SomeDbKey:1' )
1706 ->will( $this->returnValue( $cachedItem ) );
1707 $mockCache->expects( $this->at( 3 ) )
1708 ->method( 'get' )
1709 ->with( '1:AnotherDbKey:1' )
1710 ->will( $this->returnValue( null ) );
1711 $mockCache->expects( $this->never() )->method( 'set' );
1712 $mockCache->expects( $this->never() )->method( 'delete' );
1713
1714 $store = $this->newWatchedItemStore(
1715 $this->getMockLoadBalancer( $mockDb ),
1716 $mockCache
1717 );
1718
1719 $this->assertEquals(
1720 [
1721 0 => [ 'SomeDbKey' => '20151212010101', ],
1722 1 => [ 'AnotherDbKey' => null, ],
1723 ],
1724 $store->getNotificationTimestampsBatch( $user, $targets )
1725 );
1726 }
1727
1728 public function testGetNotificationTimestampsBatch_allItemsCached() {
1729 $targets = [
1730 new TitleValue( 0, 'SomeDbKey' ),
1731 new TitleValue( 1, 'AnotherDbKey' ),
1732 ];
1733
1734 $user = $this->getMockNonAnonUserWithId( 1 );
1735 $cachedItems = [
1736 new WatchedItem( $user, $targets[0], '20151212010101' ),
1737 new WatchedItem( $user, $targets[1], null ),
1738 ];
1739 $mockDb = $this->getMockDb();
1740 $mockDb->expects( $this->never() )->method( $this->anything() );
1741
1742 $mockCache = $this->getMockCache();
1743 $mockCache->expects( $this->at( 1 ) )
1744 ->method( 'get' )
1745 ->with( '0:SomeDbKey:1' )
1746 ->will( $this->returnValue( $cachedItems[0] ) );
1747 $mockCache->expects( $this->at( 3 ) )
1748 ->method( 'get' )
1749 ->with( '1:AnotherDbKey:1' )
1750 ->will( $this->returnValue( $cachedItems[1] ) );
1751 $mockCache->expects( $this->never() )->method( 'set' );
1752 $mockCache->expects( $this->never() )->method( 'delete' );
1753
1754 $store = $this->newWatchedItemStore(
1755 $this->getMockLoadBalancer( $mockDb ),
1756 $mockCache
1757 );
1758
1759 $this->assertEquals(
1760 [
1761 0 => [ 'SomeDbKey' => '20151212010101', ],
1762 1 => [ 'AnotherDbKey' => null, ],
1763 ],
1764 $store->getNotificationTimestampsBatch( $user, $targets )
1765 );
1766 }
1767
1768 public function testGetNotificationTimestampsBatch_anonymousUser() {
1769 $targets = [
1770 new TitleValue( 0, 'SomeDbKey' ),
1771 new TitleValue( 1, 'AnotherDbKey' ),
1772 ];
1773
1774 $mockDb = $this->getMockDb();
1775 $mockDb->expects( $this->never() )->method( $this->anything() );
1776
1777 $mockCache = $this->getMockCache();
1778 $mockCache->expects( $this->never() )->method( $this->anything() );
1779
1780 $store = $this->newWatchedItemStore(
1781 $this->getMockLoadBalancer( $mockDb ),
1782 $mockCache
1783 );
1784
1785 $this->assertEquals(
1786 [
1787 0 => [ 'SomeDbKey' => false, ],
1788 1 => [ 'AnotherDbKey' => false, ],
1789 ],
1790 $store->getNotificationTimestampsBatch( $this->getAnonUser(), $targets )
1791 );
1792 }
1793
1794 public function testResetNotificationTimestamp_anonymousUser() {
1795 $mockDb = $this->getMockDb();
1796 $mockDb->expects( $this->never() )
1797 ->method( 'selectRow' );
1798
1799 $mockCache = $this->getMockCache();
1800 $mockCache->expects( $this->never() )->method( 'get' );
1801 $mockCache->expects( $this->never() )->method( 'set' );
1802 $mockCache->expects( $this->never() )->method( 'delete' );
1803
1804 $store = $this->newWatchedItemStore(
1805 $this->getMockLoadBalancer( $mockDb ),
1806 $mockCache
1807 );
1808
1809 $this->assertFalse(
1810 $store->resetNotificationTimestamp(
1811 $this->getAnonUser(),
1812 Title::newFromText( 'SomeDbKey' )
1813 )
1814 );
1815 }
1816
1817 public function testResetNotificationTimestamp_noItem() {
1818 $mockDb = $this->getMockDb();
1819 $mockDb->expects( $this->once() )
1820 ->method( 'selectRow' )
1821 ->with(
1822 'watchlist',
1823 'wl_notificationtimestamp',
1824 [
1825 'wl_user' => 1,
1826 'wl_namespace' => 0,
1827 'wl_title' => 'SomeDbKey',
1828 ]
1829 )
1830 ->will( $this->returnValue( [] ) );
1831
1832 $mockCache = $this->getMockCache();
1833 $mockCache->expects( $this->never() )->method( 'get' );
1834 $mockCache->expects( $this->never() )->method( 'set' );
1835 $mockCache->expects( $this->never() )->method( 'delete' );
1836
1837 $store = $this->newWatchedItemStore(
1838 $this->getMockLoadBalancer( $mockDb ),
1839 $mockCache
1840 );
1841
1842 $this->assertFalse(
1843 $store->resetNotificationTimestamp(
1844 $this->getMockNonAnonUserWithId( 1 ),
1845 Title::newFromText( 'SomeDbKey' )
1846 )
1847 );
1848 }
1849
1850 public function testResetNotificationTimestamp_item() {
1851 $user = $this->getMockNonAnonUserWithId( 1 );
1852 $title = Title::newFromText( 'SomeDbKey' );
1853
1854 $mockDb = $this->getMockDb();
1855 $mockDb->expects( $this->once() )
1856 ->method( 'selectRow' )
1857 ->with(
1858 'watchlist',
1859 'wl_notificationtimestamp',
1860 [
1861 'wl_user' => 1,
1862 'wl_namespace' => 0,
1863 'wl_title' => 'SomeDbKey',
1864 ]
1865 )
1866 ->will( $this->returnValue(
1867 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
1868 ) );
1869
1870 $mockCache = $this->getMockCache();
1871 $mockCache->expects( $this->never() )->method( 'get' );
1872 $mockCache->expects( $this->once() )
1873 ->method( 'set' )
1874 ->with(
1875 '0:SomeDbKey:1',
1876 $this->isInstanceOf( WatchedItem::class )
1877 );
1878 $mockCache->expects( $this->once() )
1879 ->method( 'delete' )
1880 ->with( '0:SomeDbKey:1' );
1881
1882 $store = $this->newWatchedItemStore(
1883 $this->getMockLoadBalancer( $mockDb ),
1884 $mockCache
1885 );
1886
1887 // Note: This does not actually assert the job is correct
1888 $callableCallCounter = 0;
1889 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1890 $callableCallCounter++;
1891 $this->assertInternalType( 'callable', $callable );
1892 };
1893 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1894
1895 $this->assertTrue(
1896 $store->resetNotificationTimestamp(
1897 $user,
1898 $title
1899 )
1900 );
1901 $this->assertEquals( 1, $callableCallCounter );
1902
1903 ScopedCallback::consume( $scopedOverride );
1904 }
1905
1906 public function testResetNotificationTimestamp_noItemForced() {
1907 $user = $this->getMockNonAnonUserWithId( 1 );
1908 $title = Title::newFromText( 'SomeDbKey' );
1909
1910 $mockDb = $this->getMockDb();
1911 $mockDb->expects( $this->never() )
1912 ->method( 'selectRow' );
1913
1914 $mockCache = $this->getMockCache();
1915 $mockDb->expects( $this->never() )
1916 ->method( 'get' );
1917 $mockDb->expects( $this->never() )
1918 ->method( 'set' );
1919 $mockDb->expects( $this->never() )
1920 ->method( 'delete' );
1921
1922 $store = $this->newWatchedItemStore(
1923 $this->getMockLoadBalancer( $mockDb ),
1924 $mockCache
1925 );
1926
1927 // Note: This does not actually assert the job is correct
1928 $callableCallCounter = 0;
1929 $mockCallback = function( $callable ) use ( &$callableCallCounter ) {
1930 $callableCallCounter++;
1931 $this->assertInternalType( 'callable', $callable );
1932 };
1933 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback( $mockCallback );
1934
1935 $this->assertTrue(
1936 $store->resetNotificationTimestamp(
1937 $user,
1938 $title,
1939 'force'
1940 )
1941 );
1942 $this->assertEquals( 1, $callableCallCounter );
1943
1944 ScopedCallback::consume( $scopedOverride );
1945 }
1946
1947 /**
1948 * @param $text
1949 * @param int $ns
1950 *
1951 * @return PHPUnit_Framework_MockObject_MockObject|Title
1952 */
1953 private function getMockTitle( $text, $ns = 0 ) {
1954 $title = $this->getMock( Title::class );
1955 $title->expects( $this->any() )
1956 ->method( 'getText' )
1957 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1958 $title->expects( $this->any() )
1959 ->method( 'getDbKey' )
1960 ->will( $this->returnValue( str_replace( '_', ' ', $text ) ) );
1961 $title->expects( $this->any() )
1962 ->method( 'getNamespace' )
1963 ->will( $this->returnValue( $ns ) );
1964 return $title;
1965 }
1966
1967 public function testResetNotificationTimestamp_oldidSpecifiedLatestRevisionForced() {
1968 $user = $this->getMockNonAnonUserWithId( 1 );
1969 $oldid = 22;
1970 $title = $this->getMockTitle( 'SomeTitle' );
1971 $title->expects( $this->once() )
1972 ->method( 'getNextRevisionID' )
1973 ->with( $oldid )
1974 ->will( $this->returnValue( false ) );
1975
1976 $mockDb = $this->getMockDb();
1977 $mockDb->expects( $this->never() )
1978 ->method( 'selectRow' );
1979
1980 $mockCache = $this->getMockCache();
1981 $mockDb->expects( $this->never() )
1982 ->method( 'get' );
1983 $mockDb->expects( $this->never() )
1984 ->method( 'set' );
1985 $mockDb->expects( $this->never() )
1986 ->method( 'delete' );
1987
1988 $store = $this->newWatchedItemStore(
1989 $this->getMockLoadBalancer( $mockDb ),
1990 $mockCache
1991 );
1992
1993 // Note: This does not actually assert the job is correct
1994 $callableCallCounter = 0;
1995 $scopedOverride = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
1996 function( $callable ) use ( &$callableCallCounter ) {
1997 $callableCallCounter++;
1998 $this->assertInternalType( 'callable', $callable );
1999 }
2000 );
2001
2002 $this->assertTrue(
2003 $store->resetNotificationTimestamp(
2004 $user,
2005 $title,
2006 'force',
2007 $oldid
2008 )
2009 );
2010 $this->assertEquals( 1, $callableCallCounter );
2011
2012 ScopedCallback::consume( $scopedOverride );
2013 }
2014
2015 public function testResetNotificationTimestamp_oldidSpecifiedNotLatestRevisionForced() {
2016 $user = $this->getMockNonAnonUserWithId( 1 );
2017 $oldid = 22;
2018 $title = $this->getMockTitle( 'SomeDbKey' );
2019 $title->expects( $this->once() )
2020 ->method( 'getNextRevisionID' )
2021 ->with( $oldid )
2022 ->will( $this->returnValue( 33 ) );
2023
2024 $mockDb = $this->getMockDb();
2025 $mockDb->expects( $this->once() )
2026 ->method( 'selectRow' )
2027 ->with(
2028 'watchlist',
2029 'wl_notificationtimestamp',
2030 [
2031 'wl_user' => 1,
2032 'wl_namespace' => 0,
2033 'wl_title' => 'SomeDbKey',
2034 ]
2035 )
2036 ->will( $this->returnValue(
2037 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2038 ) );
2039
2040 $mockCache = $this->getMockCache();
2041 $mockDb->expects( $this->never() )
2042 ->method( 'get' );
2043 $mockDb->expects( $this->never() )
2044 ->method( 'set' );
2045 $mockDb->expects( $this->never() )
2046 ->method( 'delete' );
2047
2048 $store = $this->newWatchedItemStore(
2049 $this->getMockLoadBalancer( $mockDb ),
2050 $mockCache
2051 );
2052
2053 // Note: This does not actually assert the job is correct
2054 $addUpdateCallCounter = 0;
2055 $scopedOverrideDeferred = $store->overrideDeferredUpdatesAddCallableUpdateCallback(
2056 function( $callable ) use ( &$addUpdateCallCounter ) {
2057 $addUpdateCallCounter++;
2058 $this->assertInternalType( 'callable', $callable );
2059 }
2060 );
2061
2062 $getTimestampCallCounter = 0;
2063 $scopedOverrideRevision = $store->overrideRevisionGetTimestampFromIdCallback(
2064 function( $titleParam, $oldidParam ) use ( &$getTimestampCallCounter, $title, $oldid ) {
2065 $getTimestampCallCounter++;
2066 $this->assertEquals( $title, $titleParam );
2067 $this->assertEquals( $oldid, $oldidParam );
2068 }
2069 );
2070
2071 $this->assertTrue(
2072 $store->resetNotificationTimestamp(
2073 $user,
2074 $title,
2075 'force',
2076 $oldid
2077 )
2078 );
2079 $this->assertEquals( 1, $addUpdateCallCounter );
2080 $this->assertEquals( 1, $getTimestampCallCounter );
2081
2082 ScopedCallback::consume( $scopedOverrideDeferred );
2083 ScopedCallback::consume( $scopedOverrideRevision );
2084 }
2085
2086 public function testUpdateNotificationTimestamp_watchersExist() {
2087 $mockDb = $this->getMockDb();
2088 $mockDb->expects( $this->once() )
2089 ->method( 'select' )
2090 ->with(
2091 [ 'watchlist' ],
2092 [ 'wl_user' ],
2093 [
2094 'wl_user != 1',
2095 'wl_namespace' => 0,
2096 'wl_title' => 'SomeDbKey',
2097 'wl_notificationtimestamp IS NULL'
2098 ]
2099 )
2100 ->will(
2101 $this->returnValue( [
2102 $this->getFakeRow( [ 'wl_user' => '2' ] ),
2103 $this->getFakeRow( [ 'wl_user' => '3' ] )
2104 ] )
2105 );
2106 $mockDb->expects( $this->once() )
2107 ->method( 'onTransactionIdle' )
2108 ->with( $this->isType( 'callable' ) )
2109 ->will( $this->returnCallback( function( $callable ) {
2110 $callable();
2111 } ) );
2112 $mockDb->expects( $this->once() )
2113 ->method( 'update' )
2114 ->with(
2115 'watchlist',
2116 [ 'wl_notificationtimestamp' => null ],
2117 [
2118 'wl_user' => [ 2, 3 ],
2119 'wl_namespace' => 0,
2120 'wl_title' => 'SomeDbKey',
2121 ]
2122 );
2123
2124 $mockCache = $this->getMockCache();
2125 $mockCache->expects( $this->never() )->method( 'set' );
2126 $mockCache->expects( $this->never() )->method( 'get' );
2127 $mockCache->expects( $this->never() )->method( 'delete' );
2128
2129 $store = $this->newWatchedItemStore(
2130 $this->getMockLoadBalancer( $mockDb ),
2131 $mockCache
2132 );
2133
2134 $this->assertEquals(
2135 [ 2, 3 ],
2136 $store->updateNotificationTimestamp(
2137 $this->getMockNonAnonUserWithId( 1 ),
2138 new TitleValue( 0, 'SomeDbKey' ),
2139 '20151212010101'
2140 )
2141 );
2142 }
2143
2144 public function testUpdateNotificationTimestamp_noWatchers() {
2145 $mockDb = $this->getMockDb();
2146 $mockDb->expects( $this->once() )
2147 ->method( 'select' )
2148 ->with(
2149 [ 'watchlist' ],
2150 [ 'wl_user' ],
2151 [
2152 'wl_user != 1',
2153 'wl_namespace' => 0,
2154 'wl_title' => 'SomeDbKey',
2155 'wl_notificationtimestamp IS NULL'
2156 ]
2157 )
2158 ->will(
2159 $this->returnValue( [] )
2160 );
2161 $mockDb->expects( $this->never() )
2162 ->method( 'onTransactionIdle' );
2163 $mockDb->expects( $this->never() )
2164 ->method( 'update' );
2165
2166 $mockCache = $this->getMockCache();
2167 $mockCache->expects( $this->never() )->method( 'set' );
2168 $mockCache->expects( $this->never() )->method( 'get' );
2169 $mockCache->expects( $this->never() )->method( 'delete' );
2170
2171 $store = $this->newWatchedItemStore(
2172 $this->getMockLoadBalancer( $mockDb ),
2173 $mockCache
2174 );
2175
2176 $watchers = $store->updateNotificationTimestamp(
2177 $this->getMockNonAnonUserWithId( 1 ),
2178 new TitleValue( 0, 'SomeDbKey' ),
2179 '20151212010101'
2180 );
2181 $this->assertInternalType( 'array', $watchers );
2182 $this->assertEmpty( $watchers );
2183 }
2184
2185 public function testUpdateNotificationTimestamp_clearsCachedItems() {
2186 $user = $this->getMockNonAnonUserWithId( 1 );
2187 $titleValue = new TitleValue( 0, 'SomeDbKey' );
2188
2189 $mockDb = $this->getMockDb();
2190 $mockDb->expects( $this->once() )
2191 ->method( 'selectRow' )
2192 ->will( $this->returnValue(
2193 $this->getFakeRow( [ 'wl_notificationtimestamp' => '20151212010101' ] )
2194 ) );
2195 $mockDb->expects( $this->once() )
2196 ->method( 'select' )
2197 ->will(
2198 $this->returnValue( [
2199 $this->getFakeRow( [ 'wl_user' => '2' ] ),
2200 $this->getFakeRow( [ 'wl_user' => '3' ] )
2201 ] )
2202 );
2203 $mockDb->expects( $this->once() )
2204 ->method( 'onTransactionIdle' )
2205 ->with( $this->isType( 'callable' ) )
2206 ->will( $this->returnCallback( function( $callable ) {
2207 $callable();
2208 } ) );
2209 $mockDb->expects( $this->once() )
2210 ->method( 'update' );
2211
2212 $mockCache = $this->getMockCache();
2213 $mockCache->expects( $this->once() )
2214 ->method( 'set' )
2215 ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
2216 $mockCache->expects( $this->once() )
2217 ->method( 'get' )
2218 ->with( '0:SomeDbKey:1' );
2219 $mockCache->expects( $this->once() )
2220 ->method( 'delete' )
2221 ->with( '0:SomeDbKey:1' );
2222
2223 $store = $this->newWatchedItemStore(
2224 $this->getMockLoadBalancer( $mockDb ),
2225 $mockCache
2226 );
2227
2228 // This will add the item to the cache
2229 $store->getWatchedItem( $user, $titleValue );
2230
2231 $store->updateNotificationTimestamp(
2232 $this->getMockNonAnonUserWithId( 1 ),
2233 $titleValue,
2234 '20151212010101'
2235 );
2236 }
2237
2238 }